#pragma once

#include "MPL.h"
#include <algorithm>
#include <array>
#include <functional>
#include <tuple>
#include <vector>

#define constauto static constexpr auto

namespace EntityState 
{
	template <size_t bits> struct bitset 
	{
		using Elem = std::conditional_t<(bits < 32u), uint32_t, uint64_t>;
		constauto wordbits = (std::ptrdiff_t)(CHAR_BIT * sizeof(Elem));
		constauto words = (std::ptrdiff_t)(((bits - 1) / wordbits) + 1);
		using data_type = std::array<Elem, words>;
		data_type data;

		class reference 
		{ // proxy for an element
			friend bitset<bits>;
			bitset<bits> *pBits; // pointer to the bitset
			size_t pos;          // position of element in bitset
		public:
			~reference() noexcept {} // destroy the object

			reference(bitset<bits> *p, size_t i) : pBits(p), pos(i) {}

			reference &operator=(bool val) noexcept // assign Boolean to element
			{ 
				pBits->set(pos, val);
				return (*this);
			}

			operator bool() const noexcept // return element
			{ 
				return (pBits->get(pos));
			}
		};

		void set(size_t pos, bool val) noexcept 
		{
			if (val)
				data[pos / wordbits] |= (Elem)1 << pos % wordbits;
			else
				data[pos / wordbits] &= ~((Elem)1 << pos % wordbits);
		}

		constexpr bool get(size_t pos) const noexcept 
		{
			return data[pos / wordbits] & ((Elem)1 << pos % wordbits);
		}

		inline void reset() noexcept 
		{
			for (std::ptrdiff_t pos = words - 1; pos >= 0; --pos)
				data[pos] = (Elem)0u;
		}

		constexpr bool operator[](const size_t pos) const noexcept 
		{
			return get(pos);
		}

		reference operator[](size_t pos) noexcept 
		{ 
			return { this, pos }; 
		}

		inline constexpr bool operator==(const bitset &other) const noexcept 
		{
			for (std::ptrdiff_t pos = words - 1; pos >= 0; --pos)
				if (data[pos] != other.data[pos])
					return false;
			return true;
		}

		inline constexpr bitset &operator&=(const bitset &other) noexcept 
		{
			for (std::ptrdiff_t pos = words - 1; pos >= 0; --pos)
				data[pos] &= other.data[pos];
			return *this;
		}

		template <size_t bitpos, size_t pos>
		static constexpr Elem make_bit() noexcept 
		{
			constexpr size_t start = pos * wordbits;
			constexpr size_t end = (pos + 1) * wordbits;
			constexpr bool valid = bitpos >= start && bitpos < end;
			return valid ? ((Elem)1u << (bitpos - start)) : (Elem)0u;
		}
		template <size_t pos, size_t... bitpos>
		static constexpr Elem make_elem(std::index_sequence<bitpos...>) noexcept 
		{
			return (0u | ... | make_bit<bitpos, pos>());
		}
		template <typename T, size_t... pos>
		static constexpr data_type make_data(T bp,
			std::index_sequence<pos...>) noexcept 
		{
			return { {make_elem<pos>(bp)}... };
		}
		template <size_t... bitpos> static constexpr bitset make() noexcept 
		{
			return { make_data(std::index_sequence<bitpos...>{},
							  std::make_index_sequence<words>()) };
		}

		constexpr bitset(data_type d) : data(d) {}
		constexpr bitset() : data() {}
	};

	template <size_t bits>
	inline constexpr bitset<bits> operator&(const bitset<bits> &left,
		const bitset<bits> &right) noexcept 
	{
		bitset<bits> ans = left;
		return (ans &= right);
	}
	struct state;
	struct global_state;

	struct another;

	template <typename T, typename = void> struct state_type_of 
	{
		using type = state;
	};

	template <typename T>
	struct state_type_of<T, std::void_t<typename T::state_type>> 
	{
		using type = typename T::state_type;
	};

	template <typename... Ts> struct Trait 
	{
		using ARG = MPL::typelist<Ts...>;

		template <typename T>
		using is_global_state_ = std::is_same<typename state_type_of<T>::type, global_state>;

		template <typename T>
		using is_state_ = std::is_same<typename state_type_of<T>::type, state>;

		using States = MPL::fliter_t<is_state_, ARG>;
		using GlobalStates = MPL::fliter_t<is_global_state_, ARG>;

		using AllStates = MPL::concat_t<States, GlobalStates>;

		static_assert(
			MPL::rewrap_t<
			std::conjunction,
			MPL::map_t<std::is_pod, AllStates>>::value,
			"state should only be pod.");

		static_assert(
			MPL::rewrap_t<
			std::conjunction,
			MPL::map_t<MPL::is_complete, AllStates>>::value,
			"state should only be complete.");

		template <typename T> struct is_state 
		{
			constauto value = MPL::contain_v<T, States>;
		};
		template <typename T> struct is_global_state 
		{
			constauto value = MPL::contain_v<T, GlobalStates>;
		};

		constauto stateSize {MPL::size<States>{}};
		constauto globalStateSize {MPL::size<GlobalStates>{}};

		using Bitset = bitset<stateSize>;

		template <typename T> constauto sign_index() 
		{
			return MPL::template index<T, States>{};
		}

		template <typename... Ys> struct SignBits 
		{
			static constexpr Bitset value =
				Bitset::template make<sign_index<Ys>()...>();
		};

		template <typename... Ys> struct Reference 
		{
			using Types = MPL::typelist<Ys...>;
			size_t ref[sizeof...(Ys)];
			Bitset cap;

			Reference() 
			{ 
				cap.reset(); 
			}

			template <typename T> inline bool caped() const noexcept 
			{
				return cap[MPL::index<T, Types>{}];
			}

			template <typename T> inline void add(const size_t n) noexcept 
			{
				constauto index {MPL::index<T, Types>{}};
				ref[index] = n;
				cap[index] = true;
			}

			template <typename T> inline size_t get() const noexcept 
			{
				return ref[MPL::index<T, Types>{}];
			}
		};
		using StateReference = MPL::rewrap_t<Reference, States>;

		struct StateStorage {
			template <typename... Ys>
			using TupleOfVector = std::tuple<std::vector<Ys>...>;
			MPL::rewrap_t<TupleOfVector, States> data;

			template <typename T> inline T &ref(size_t index) noexcept 
			{
				return std::get<std::vector<T>>(data)[index];
			}

			template <typename T, typename... Ys>
			inline size_t add(Ys &&... args) noexcept 
			{
				std::vector<T> &vec = std::get<std::vector<T>>(data);
				vec.emplace_back(T{ std::forward<Ys>(args)... });
				return vec.size() - 1;
			}
		};

		struct Entity 
		{
			Bitset bits;
			StateReference compRef;
		};
	};

	struct simple_for 
	{
		template <typename F>
		inline void operator()(F &&f, size_t start, size_t end) noexcept 
		{
			for (size_t i{ start }; i < end; i++)
				f(i);
		}
	};

	struct EntityHandle
	{
		size_t pos;
		size_t version;
	};

	template <typename... Ts> struct StateManager 
	{
		using Trait = Trait<Ts...>;
		using InnerEntity = typename Trait::Entity;
		typename Trait::StateStorage states;
		std::vector<InnerEntity> entities;
		std::vector<size_t> version;
		std::vector<size_t> hole;
		size_t size = 0;
		MPL::rewrap_t<std::tuple, typename Trait::GlobalStates> globals;
		using Group = std::pair<typename Trait::Bitset, std::vector<size_t>>;
		std::vector<Group> groups;


		template <typename F> struct transition_trait {
			using FunctionTrait = MPL::generic_function_trait<std::decay_t<F>>;
			using ArgumentType = typename FunctionTrait::argument_type;
			using DecayArgument = MPL::map_t<std::decay_t, ArgumentType>;
			using States =
				MPL::intersection_t<DecayArgument, typename Trait::States>;
		};

		struct Entity 
		{
			StateManager &states;
			size_t pos;
			size_t version;
			Entity(StateManager &s, size_t p, size_t ver)
				: states(s), pos(p), version(ver) {}
			Entity(const Entity &o)
				: states(o.states), pos(o.pos), version(o.version) {}
			Entity(StateManager &s, EntityHandle h)
				:states(s), pos(h.pos), version(h.version) {}
			operator EntityHandle()
			{
				return { pos, version };
			}
			InnerEntity &get_entity() noexcept 
			{ 
				return states.entities[pos]; 
			}
			template <typename T> inline void add(T &&s) noexcept 
			{
				InnerEntity &e = get_entity();
				states.add<T>(e, pos, std::forward<T>(s));
			}
			template <typename T, typename... Ys>
			inline void add(Ys &&... args) noexcept 
			{
				InnerEntity &e = get_entity();
				states.add<T>(e, pos, std::forward<Ys>(args)...);
			}
			template <typename T> inline T &ref() noexcept 
			{
				InnerEntity &e = get_entity();
				return states.ref<T>(e);
			}
			template <typename T> inline void erase() noexcept 
			{
				InnerEntity &e = get_entity();
				states.erase<T>(e, pos);
			}
			template <typename T> inline bool has() noexcept 
			{
				InnerEntity &e = get_entity();
				return states.has<T>(e);
			}
			inline void kill() noexcept 
			{
				InnerEntity &e = get_entity();
				states.kill_entity(e);
			}

			inline bool is_valid() const noexcept 
			{
				InnerEntity &e = get_entity();
				return states.version[pos] == version && !(e.bits == 0);
			}

			template <typename... Ys> struct FunctionCallerHelper 
			{
				template <typename F>
				void operator()(InnerEntity &e, StateManager &manager, F &&f) 
				{
					f(manager.ref<std::decay_t<Ys>>(e)...);
				}
			};

			template <typename F>
			inline bool transit(F &&f) noexcept 
			{
				using TransitionTrait = transition_trait<F>;
				using ArgumentType = typename TransitionTrait::ArgumentType;
				using States = typename TransitionTrait::States;
				constauto bits {MPL::rewrap_t<Trait::template SignBits, States>::value};
				InnerEntity &e = get_entity();
				if ((e.bits & bits) == bits)
				{
					MPL::rewrap_t<FunctionCallerHelper, ArgumentType>{}(e, *this, std::forward<F>(f));
					return true;
				}
				return false;
			}

			template <typename F>
			inline bool operator->*(F&& f) noexcept 
			{
				return transit(f);
			}
		};

		Entity get_entity(EntityHandle h)
		{
			return Entity(*this, h);
		}

		StateManager() noexcept 
		{
			init_groups(typename Trait::States{});
			grow_to(500u);
		}

		template <typename... Ys> void init_groups(MPL::typelist<Ys...>) noexcept 
		{
			std::initializer_list<int> _{
				(groups.push_back(Group{Trait::template SignBits<Ys>::value, {}}),
				 1)... };
			(void)_;
		}

		std::vector<size_t> &get_group(typename Trait::Bitset all) const noexcept 
		{
			const size_t groupsize{ groups.size() };
			size_t minSize = 0ull - 1ull;
			std::vector<size_t> *select = nullptr;
			for (size_t i{ 0u }; i < groupsize; i++) 
			{
				const auto bits = groups[i].first;
				const bool wasInGroup = (all & bits) == bits;
				if (!wasInGroup)
					continue;
				auto &group = const_cast<std::vector<size_t> &>(groups[i].second);
				const size_t size = group.size();
				if (size < minSize) 
				{
					select = &group;
					minSize = size;
				}
			}
			return (*select);
		}

		template <typename... Ys> void create_group() noexcept 
		{
			constauto bits = Trait::template SignBits<Ys...>::value;
			groups.push_back(Group{ bits, {} });
			auto &group = groups[groups.size() - 1].second;
			for (size_t i{ 0u }; i < size; i++) 
			{
				InnerEntity &e(entities[i]);
				const bool wasInGroup = ((bits & e.bits) == bits);
				if (wasInGroup)
					group.push_back(i);
			}
		}

		template <typename... Ys> struct FunctionCallerHelper 
		{
			template <typename T>
			decltype(auto) get_helper(InnerEntity &e, size_t pos,
				StateManager &manager) 
			{
				if constexpr (std::is_same_v<std::decay_t<T>, Entity>)
					return Entity{ manager, pos, manager.version[pos] };
				else if constexpr (Trait::template is_global_state<
					std::decay_t<T>>::value)
					return manager.ref<std::decay_t<T>>();
				else
					return manager.ref<std::decay_t<T>>(e);
			}

			template <typename F>
			void operator()(InnerEntity &e, size_t pos, StateManager &manager, F &&f) 
			{
				f(get_helper<Ys>(e, pos, manager)...);
			}
		};

		template <typename... Ys> struct GFunctionCallerHelper 
		{
			template <typename T> decltype(auto) get_helper(StateManager &manager) 
			{
				return manager.ref<std::decay_t<T>>();
			}

			template <typename F> void operator()(StateManager &manager, F &&f) 
			{
				f(get_helper<Ys>(manager)...);
			}
		};


		template <typename F, typename Loop = simple_for>
		inline void transit(F &&f, Loop loop = simple_for{}) noexcept 
		{
			using TransitionTrait = transition_trait<F>;
			using ArgumentType = typename TransitionTrait::ArgumentType;
			using States = typename TransitionTrait::States;
			if constexpr (MPL::size<States>{} == 0u) 
			{
				if constexpr (MPL::contain_v<Entity, ArgumentType>)
					loop([this, f = std::forward<F>(f)](size_t i) 
				{
					InnerEntity &e{ entities[i] };
					MPL::rewrap_t<FunctionCallerHelper, ArgumentType>{}(e, i, *this, std::move(f));
				},
						0u, size);
				else
					MPL::rewrap_t<GFunctionCallerHelper, ArgumentType>{}(*this, std::forward<F>(f));
			}
			else 
			{
				constauto bits {MPL::rewrap_t<Trait::template SignBits, States>::value};
				std::vector<size_t> &group{ get_group(bits) };
				size_t groupSize{ group.size() };
				if (groupSize == 0u)
					return;
				loop([this, &group, f = std::forward<F>(f)](size_t i) 
				{
					InnerEntity &e{ entities[group[i]] };
					if ((e.bits & bits) == bits)
						MPL::rewrap_t<FunctionCallerHelper, ArgumentType>{}(e, group[i], *this,
							std::move(f));
				},
				0u, groupSize);
			}
		}

		template <typename F>
		inline void operator->*(F&& f) noexcept {
			transit(f);
		}

		std::function<void(void)> grow_hook = [] {};
		void grow_to(size_t newCapacity) noexcept 
		{
			grow_hook();
			const auto capacity = entities.capacity();
			entities.resize(newCapacity);
			version.resize(newCapacity);
			for (auto i(capacity); i < newCapacity; i++) 
			{
				auto &e(entities[i]);
				e.bits.reset();
				version[i] = 0;
			}
		}

		void grow() noexcept 
		{
			const auto capacity = entities.capacity();
			if (size < capacity)
				return;
			size_t newCapacity = capacity * 2 + 50;
			grow_to(newCapacity);
		}

		Entity create_entity() noexcept 
		{
			grow();
			size_t freePos;
			if (!hole.empty()) {
				freePos = hole.back();
				hole.pop_back();
			}
			else
				freePos = size++;
			++version[freePos];
			return { *this, freePos, version[freePos] };
		}

		inline void kill_entity(InnerEntity &e) noexcept 
		{ 
			hole.emplace_back(e.pos); 
		}

		template <typename T, typename... Ys>
		inline void add(InnerEntity &e, size_t pos, Ys &&... args) noexcept 
		{
			auto pre = e.bits;
			e.bits[Trait::template sign_index<T>()] = true;
			regroup(e, pos, pre);
			if (!e.compRef.template caped<T>())
				e.compRef.template add<T>(
					states.template add<T>(std::forward<Ys>(args)...));
			else
				ref<T>(e) = T{ std::forward<Ys>(args)... };
		}

		template <typename T> 
		inline T &ref(InnerEntity &e) noexcept 
		{
			return states.template ref<T>(e.compRef.template get<T>());
		}

		template <typename T> 
		inline T &ref() noexcept 
		{
			return std::get<T>(globals);
		}

		template <typename T> 
		inline void erase(InnerEntity &e, size_t pos) noexcept 
		{
			auto pre = e.bits;
			e.bits[Trait::template sign_index<T>()] = false;
			regroup(e, pos, pre);
		}

		template <typename T> 
		inline bool has(InnerEntity &e) noexcept 
		{
			return e.bits[Trait::template sign_index<T>()];
		}

		void regroup(InnerEntity &e, size_t pos,
			typename Trait::Bitset pre) noexcept 
		{
			const auto groupsize{ groups.size() };
			for (size_t i{ 0u }; i < groupsize; i++) 
			{
				auto &&[bits, group] = groups[i];

				const bool wasInGroup = (pre & bits) == bits;
				const bool isEnteringGroup = (e.bits & bits) == bits;
				const bool isLeavingGroup = !isEnteringGroup;

				if (!wasInGroup && isEnteringGroup) 
				{
					group.push_back(pos);
				}
				else if (wasInGroup && isLeavingGroup) 
				{
					auto i = std::find(group.begin(), group.end(), pos);
					*i = group.back();
					group.pop_back();
				}
			}
		}

		void tick() noexcept 
		{
			size_t holeSize = hole.size();
			for (size_t i{ 0u }; i < holeSize; i++) 
			{
				InnerEntity &e = entities[hole[i]];
				if (e.bits == typename Trait::Bitset{})
					continue;
				auto pre = e.bits;
				e.bits.reset();
				regroup(e, pre);
			}
		}
	};


} // namespace EntityState

#undef constauto